using System;
using System.Collections.Generic;
using System.Text;
using MacomberMap.Common.Types;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;
using System.Xml;
using System.Drawing;
using System.Reflection;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    /// <summary>
    /// This class holds the tuning parameters for loading in one-lines.
    /// </summary>
    public class MM_OneLine_TuningParameter : MM_Serializable
    {
        #region Variable declarations
        /// <summary>The element type</summary>
        public MM_OneLine_Element.ElementTypeEnum ElemType;

        /// <summary>The element's orientation</summary>
        public MM_OneLine_Element.enumOrientations Orientation = MM_OneLine_Element.enumOrientations.Unknown;

        /// <summary>The minimum angle for acceptance</summary>
        public double MinAngle = double.NaN;

        /// <summary>The minimum angle for acceptance</summary>
        public double MaxAngle = double.NaN;

        /// <summary>The minimum distance for acceptance</summary>
        public double MinDist = double.NaN;

        /// <summary>The minimum distance for acceptance</summary>
        public double MaxDist = double.NaN;

        /// <summary>The minimum delta X for acceptance</summary>
        public double MinDeltaX = double.NaN;

        /// <summary>The maximum delta X for acceptance</summary>
        public double MaxDeltaX = double.NaN;

        /// <summary>The minimum delta Y for acceptance</summary>
        public double MinDeltaY = double.NaN;

        /// <summary>The maYimum delta Y for acceptance</summary>
        public double MaxDeltaY = double.NaN;

        /// <summary>Whether the bounds of the test rectangle's X is within those of the origin rectangle</summary>
        public bool WithinX = false;

        /// <summary>Whether the bounds of the test rectangle's X is within those of the origin rectangle</summary>
        public bool WithinY = false;

        /// <summary>The name of the picture needed to be found</summary>
        public String Name;
        #endregion

        #region Identity
        /// <summary>
        /// Return the identity of the tuning parameter
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder OutString = new StringBuilder();
            foreach (FieldInfo fI in this.GetType().GetFields())
            {
                Object inVal = fI.GetValue(this);
                if (inVal == null)
                { }
                else if (inVal is double)
                {
                    if (!double.IsNaN((double)inVal))
                        OutString.Append(fI.Name + "=" + inVal.ToString() + " ");
                }
                else if (inVal is MM_OneLine_Element.enumOrientations)
                {
                    if ((MM_OneLine_Element.enumOrientations)inVal != MM_OneLine_Element.enumOrientations.Unknown)
                        OutString.Append(fI.Name + "=" + inVal.ToString() + " ");
                }
                else
                    OutString.Append(fI.Name + "=" + inVal.ToString() + " ");
            }
            return OutString.ToString();
        }
        #endregion
        #region Initialization
        /// <summary>
        /// Initialize a new MM tuning parameter
        /// </summary>
        /// <param name="BaseElement"></param>
        public MM_OneLine_TuningParameter(XmlElement BaseElement)
            : base(BaseElement)
        { }
        #endregion

        #region Validation
        /// <summary>
        /// Check to see whether an element fits the validation procedure
        /// </summary>
        /// <param name="ComparisonElement">The element for comparison</param>
        /// <param name="BaseElement">The element which will receive the component if accepted</param>
        /// <returns></returns>
        public bool Check(MM_OneLine_XmlComponent ComparisonElement, MM_OneLine_XmlComponent BaseElement)
        {
            if (ComparisonElement == null || BaseElement == null)
                return false;
            else if (!string.Equals(ComparisonElement.Name, Name, StringComparison.CurrentCultureIgnoreCase))
                return false;
            else
            {
                //Determine the two closest points between the rectangles, and compute angle and distance.
                double Dist, Angle, DeltaX, DeltaY;
                bool WithinX, WithinY;

                if (ComparisonElement.ElementRectangle.Contains(BaseElement.ElementRectangle))
                    return true;
                ComputeDistanceAndAngle(BaseElement.ElementRectangle, ComparisonElement.ElementRectangle, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);


                //Check our minimum distances: delta X, delta Y                
                if (!double.IsNaN(MinDeltaX) && DeltaX < MinDeltaX)
                    return false;
                else if (!double.IsNaN(MaxDeltaX) && DeltaX > MaxDeltaX)
                    return false;
                else if (!double.IsNaN(MinDeltaY) && DeltaY < MinDeltaY)
                    return false;
                else if (!double.IsNaN(MaxDeltaY) && DeltaY > MaxDeltaY)
                    return false;
                else if (!double.IsNaN(MinDist) && Dist < MinDist)
                    return false;
                else if (!double.IsNaN(MaxDist) && Dist > MaxDist)
                    return false;
                else if (!double.IsNaN(MaxAngle) && !double.IsNaN(MinAngle) && MinAngle > MaxAngle)
                    return (Angle >= MinAngle || Angle <= MinAngle);
                else if (!double.IsNaN(MinAngle) && Angle < MinAngle)
                    return false;
                else if (!double.IsNaN(MaxAngle) && Angle > MaxAngle)
                    return false;
                else if (this.WithinY && !WithinY)
                    return false;
                else if (this.WithinX && !WithinX)
                    return false;
                else
                    return true;
            }
        }

        /// <summary>
        /// Compute the closest distance and angle between two points
        /// </summary>
        /// <param name="BaseRect">Our base rectangle</param>
        /// <param name="OtherRect">The rectangle for comparison</param>
        /// <param name="Dist">The distance between the two rectangles</param>
        /// <param name="Angle">The angle between the two rectangles</param>
        /// <param name="DeltaX">The difference between Xs</param>
        /// <param name="DeltaY">The difference between Ys</param>
        /// <param name="WithinX">Whether the other X is within the base left to right</param>
        /// <param name="WithinY">Whether the other Y is within the base top to bottom</param>
        public static void ComputeDistanceAndAngle(Rectangle BaseRect, Rectangle OtherRect, out double Dist, out double Angle, out double DeltaX, out double DeltaY, out bool WithinX, out bool WithinY)
        {
            DeltaX = ClosestToZero(OtherRect.Left - BaseRect.Left, OtherRect.Left - BaseRect.Right, OtherRect.Right - BaseRect.Left, OtherRect.Right - BaseRect.Right);
            DeltaY = ClosestToZero(BaseRect.Top - OtherRect.Top, BaseRect.Top - OtherRect.Bottom, BaseRect.Bottom - OtherRect.Top, BaseRect.Bottom - OtherRect.Bottom);
            Dist = Math.Sqrt((DeltaX * DeltaX) + (DeltaY * DeltaY));
            WithinX = (BaseRect.Left >= OtherRect.Left && BaseRect.Right < OtherRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Right < BaseRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Left <= BaseRect.Right);
            if (WithinX)
                DeltaX = 0;
            WithinY = (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom) || (OtherRect.Bottom >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom);
            if (WithinY)
                DeltaY = 0;
            //(BaseRect.Top >= OtherRect.Top && BaseRect.Bottom < OtherRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Bottom < BaseRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom);

            //WithinX = OtherRect.Left >= BaseRect.Left && OtherRect.Right <= BaseRect.Right;
            //WithinY = OtherRect.Top >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom;
            Angle = 180.0 * Math.Atan2(DeltaY, DeltaX) / Math.PI;
            if (Angle < 0)
                Angle += 360;
        }

        /// <summary>
        /// Determine the closest points to zero
        /// </summary>
        /// <param name="InNumbers"></param>
        /// <returns></returns>
        private static double ClosestToZero(params double[] InNumbers)
        {
            for (int a = 1; a < InNumbers.Length; a++)
                if (Math.Abs(InNumbers[a]) < Math.Abs(InNumbers[0]))
                    InNumbers[0] = InNumbers[a];
            return InNumbers[0];
        }

        #endregion

    }

}
